import pkg from '../../../../package.json';
import { evolve, assocPath, pathOr } from 'ramda';
import set from 'lodash/set';
import permissionCheckResolveDecorate from '@kuiper/auth/dist/server/lib/permissionCheckResolveDecorate';

import urlFormat from '../../../common/urlFormat';
import errorCodesBuilder from '../../helpers/errorCodesBuilder';
import ensureWithBalance from '../../plugins/billing/ensureWithBalance';
import { resolve as assetUpdateOne } from '../Asset/updateOne';
import fillAviliableFieldDefaultValue from './fillAviliableFieldDefaultValue';
import dataValidate, { assetValidate } from './dataValidate';
import { update } from './update';

const { name } = pkg;
const errorCodes = errorCodesBuilder('创建任务配置失败');

function filterEmptyField(data) {
	return Object.entries(data).reduce((obj, [key, val]) => {
		if (Array.isArray(val) ? val.length : val !== undefined) {
			obj[key] = val;
		}
		return obj;
	}, {});
}

function fillDefaultValue({ immediateExec, defaultValues, data }) {
	let ret = { ...defaultValues, ...filterEmptyField(data) };

	if (immediateExec) {
		ret.enableMonitor = true;
		ret.enableAlert = true;
	}

	const target = urlFormat({
		protocol: ret.target.protocol,
		host: ret.target.host,
	});
	if (!pathOr('', ['httpSettings', 0, 'target'], ret)) {
		ret = assocPath(['httpSettings', 0, 'target'], target, ret);
	}
	if (!pathOr('', ['pingSettings', 0, 'target'], ret)) {
		ret = assocPath(['pingSettings', 0, 'target'], target, ret);
	}

	fillAviliableFieldDefaultValue({ record: ret });

	ret = assocPath(
		['sslSettings', 'enable'],
		ret.target.protocol === 'https:',
		ret
	);

	if (ret.sourceIp) {
		ret = assocPath(['httpSettings', 0, 'sourceIp'], ret.sourceIp, ret);
		ret = assocPath(['pingSettings', 0, 'sourceIp'], ret.sourceIp, ret);
	}

	return ret;
}

export async function resolve(root, args, context) {
	const {
		request: {
			log,
			auth: {
				credentials: { permissionMap },
			},
			server: {
				methods: { isCydEnabled, balanceTaskCountUpdate },
				app: {
					model: { Asset, Balance, Job, AlertGroup, SitePortrait },
					config: {
						[name]: {
							defaultAssetSettings,
							__browser: { defaultJobSettings },
						},
					},
				},
			},
		},
	} = context;

	const uid = args.record.uid;
	let record = fillDefaultValue({
		immediateExec: args.immediateExec,
		defaultValues: JSON.parse(JSON.stringify(defaultJobSettings)),
		data: args.record,
	});

	let balance;
	try {
		balance = await Balance.findOne({ uid });
		if (!balance) throw new Error(`create job not found balance: uid()`);
	} catch (err) {
		const error = errorCodes['[03]添加资产：查询关联balance记录失败'].from(err);
		log.error({ error }, error.rawMessage);
		throw error;
	}

	if (
		record.verification &&
		record.verification.isVerified &&
		!permissionMap[`cloud-monitor\x01createJob\x010`]
	) {
		record.verification.isVerified = false;
	}

	try {
		const uid = record.uid;
		delete record.uid;
		record = {
			uid,
			...dataValidate({ balance, record }),
		};
		record.targetUrl = urlFormat(record.target);
	} catch (err) {
		const error = errorCodes['[03]添加资产：传入参数错误'].from(err).addition({
			reason: '传入参数错误',
			host: record.target.host,
		});
		context.request.log.error({ error }, error.rawMessage);
		error.message += ` (${err.message})`;
		throw error;
	}

	let alertGroup;
	try {
		const [exists, group] = await Promise.all([
			Job.findOne({
				uid,
				'target.protocol': record.target.protocol,
				'target.host': record.target.host,
			}),
			AlertGroup.findOne({ uid, isDefault: true }),
		]);
		alertGroup = group;
		if (exists) {
			throw errorCodes['[03]添加资产失败：当前资产已添加'].messageSuffix(
				record.target.host
			);
		}
	} catch (err) {
		const error = errorCodes['[03]添加资产：检查资产是否已添加失败']
			.from(err)
			.addition({
				reason: '当前资产已添加',
				host: record.target.protocol + '//' + record.target.host,
			});
		log.error({ error, target: record.target }, error.rawMessage);
		throw error;
	}

	// 资产变动记录
	const assetTarget = record.targetUrl;
	const asset = new Asset(
		assetValidate({
			record: {
				uid,
				target: assetTarget,
				targetType: record.target.ipType === 0 ? 'domain' : 'ip',
				alertSettings: record.alertSettings,
				...evolve(
					{
						taskSettings: {
							target: () => assetTarget,
						},
						alertSettings: {
							notification: () => record.alertSettings.notification,
						},
					},
					defaultAssetSettings
				),
			},
			balance,
		})
	);
	asset.alertSettings.alertGroupIds = alertGroup ? [alertGroup._id] : [];
	log.debug({ target: assetTarget }, '创建关联资产变动记录');
	record.assetId = asset.id;

	const job = new Job(record);
	try {
		job.cydEnabled = await isCydEnabled(
			record.target.hostname,
			record.uid.toString()
		);
	} catch (err) {
		job.cydEnabled = false;
		const error = errorCodes['[03]创建任务：检查是否启用创宇盾失败'].from(err);
		log.error({ error, asset: assetTarget }, error.rawMessage);
	}

	if (job.cydEnabled) {
		job.verification.isVerified = true;
		job.verification.verifyType = 'yunaq';
	}

	if (!job.verification.isVerified) {
		job.enableMonitor = false;
		job.enableAlert = false;
	}

	// 网站画像
	const sitePortrait = new SitePortrait({
		uid,
		jobId: job._id,
		alertSettings: { ...record.alertSettings, enable: true },
		target: record.targetUrl,
		portrait: '{}',
		version: -1,
		taskSettings: { enable: job.cydEnabled },
	});
	job.sitePortraitId = sitePortrait._id;

	// 保存资产
	try {
		asset.jobId = job._id;
		job.alertSettings.alertGroupIds = alertGroup ? [alertGroup._id] : [];
		await Promise.all([asset.save(), job.save(), sitePortrait.save()]);
	} catch (err) {
		const error = errorCodes['[03]更新资产扫信息失败'].from(err);
		log.error({ error, asset: assetTarget }, error.rawMessage);
		throw error;
	}

	if (job.verification.isVerified && job.enableMonitor) {
		try {
			await update({
				context,
				immediateExec: args.immediateExec,
				prevRecord: { ...job.toObject(), _id: job._id },
				record: job.toObject(),
			});
			await assetUpdateOne(
				null,
				{
					immediateExec: true,
					enableMonitor: true,
					filter: { _id: asset._id },
					record: {},
				},
				context
			);
		} catch (err) {
			const error = errorCodes['[03][创建监测任务配置]更新任务状态失败'].from(
				err
			);
			log.error({ error, target: record.target }, error.rawMessage);
			throw error;
		}
	}

	await balanceTaskCountUpdate(job.uid).catch(err => {
		const error = errorCodes['[03][创建监测任务配置]更新任务状态失败'].from(
			err
		);
		log.error({ error, target: record.target }, error.rawMessage);
		throw error;
	});

	log.audit(
		{
			module: 'job',
			actionType: 'create',
			actionMessage: '新增资产',
			affects: [record.targetUrl],
			detail: `新增资产，资产名称：${record.note}，资产地址：${urlFormat({
				protocol: record.target.protocol,
				host: record.target.host,
			})}。`,
		},
		`[${record.target.host}]创建监测任务`
	);

	return { recordId: job.id, record: job };
}

const resolver = permissionCheckResolveDecorate({
	required: 'cloud-monitor\x01createJob',
	allMatchArgsBuilder(
		_source,
		args,
		{
			request: {
				auth: {
					credentials: { uid },
				},
			},
		}
	) {
		if (!args.record.uid) {
			set(args, ['record', 'uid'], uid);
		}
	},
	selfOnlyArgsBuilder(
		_source,
		args,
		{
			request: {
				auth: {
					credentials: { uid },
				},
			},
		}
	) {
		set(args, ['record', 'uid'], uid);
	},
})(
	ensureWithBalance((root, args, context) => ({
		server: context.request.server,
		uid: args.record.uid,
		ops: [{ key: 'domain', value: -1 }],
	}))(resolve)
);
export default {
	description: '添加资产',
	type: 'CreateOneJobPayload',
	args: {
		record: ' CreateOneJobInput!',
	},
	resolve: resolver,
};
